// sunoApi.js
/**
 * Suno 核心服务类，负责 token/session 保活，自动处理验证码
 */
// const axios = require('axios');
// const UserAgent = require('user-agents');
 
// const cookie = require('cookie');
// const { randomUUID } = require('crypto');
// const { Solver } = require('@2captcha/captcha-solver');
// // 需要 npm i rebrowser-playwright-core ghost-cursor-playwright 等依赖
// // const { chromium } = require('playwright');

// const { createCursor } = require('ghost-cursor-playwright');
// const path = require('path');
// const fs = require('fs').promises;

const logger = console; // 用 console 替代 pino，简单易用
function yn(val, options = { default: false }) {
    if (val === undefined) return options.default;
    if (typeof val === 'boolean') return val;
    if (typeof val === 'number') return !!val;
    if (typeof val === 'string') {
        const lower = val.trim().toLowerCase();
        if (lower === 'true' || lower === '1' || lower === 'yes' || lower === 'y') return true;
        if (lower === 'false' || lower === '0' || lower === 'no' || lower === 'n') return false;
    }
    return options.default;
}
// SunoApi核心实现（省略类型定义，仅用 JS 风格）
class SunoApi {
    static BASE_URL = 'https://studio-api.prod.suno.com';
    static CLERK_BASE_URL = 'https://clerk.suno.com';
    static CLERK_VERSION = '5.93.0'; // 可视需要更新

    constructor(cookiesStr) {
        // 初始化 UserAgent/Cookie
        this.userAgent = new UserAgent(/Macintosh/).random().toString();
        this.cookies = cookie.parse(cookiesStr);
        this.deviceId = this.cookies.ajs_anonymous_id || randomUUID();
        this.currentToken = null;
        this.sid = null;

        // 2captcha 验证码 solver，需配置好环境变量
        this.solver = new Solver(process.env.TWOCAPTCHA_KEY || '');

        // Axios 请求实例
        this.client = axios.create({
            withCredentials: true,
            timeout: 20000,
            headers: {
                'Device-Id': this.deviceId,
                'User-Agent': this.userAgent
            }
        });
        // 请求、响应拦截器：自动设置 Cookie 和 Authorization
        this.client.interceptors.request.use(config => {
            if (this.currentToken && !config.headers.Authorization)
                config.headers.Authorization = `Bearer ${this.currentToken}`;
            const cookiesArr = Object.entries(this.cookies).map(([k, v]) => cookie.serialize(k, v));
            config.headers.Cookie = cookiesArr.join('; ');
            return config;
        });
        this.client.interceptors.response.use(resp => {
            const setCookie = resp.headers['set-cookie'];
            if (Array.isArray(setCookie)) {
                const newCookies = cookie.parse(setCookie.join('; '));
                for (const k in newCookies) this.cookies[k] = newCookies[k];
            }
            return resp;
        });

        this.cursor = null;
    }

    // 初始化流程：必须确保登录态可用
    async init() {
        await this.getAuthToken();
        await this.keepAlive();
        return this;
    }

    // 获取后端session_id，用于后续请求 session/token 保活
    async getAuthToken() {
        const getSessionUrl = `${SunoApi.CLERK_BASE_URL}/v1/client?_is_native=true&_clerk_js_version=${SunoApi.CLERK_VERSION}`;
        // 用 __client 字段权限获取
        const sessionRes = await this.client.get(getSessionUrl, {
            headers: { Authorization: this.cookies.__client }
        });
        if (!sessionRes?.data?.response?.last_active_session_id) throw new Error('获取 Suno session 失败，请重新登录获取 Cookie');
        this.sid = sessionRes.data.response.last_active_session_id;
        console.log(this.sid)
    }

    // 保活 session 和 token
    async keepAlive() {
        if (!this.sid) throw new Error('Session ID 尚未设置，无法续期');
        const renewUrl = `${SunoApi.CLERK_BASE_URL}/v1/client/sessions/${this.sid}/tokens?_is_native=true&_clerk_js_version=${SunoApi.CLERK_VERSION}`;
        logger.log('正在保活 Suno token...');
        const renewRes = await this.client.post(renewUrl, {}, {
            headers: { Authorization: this.cookies.__client }
        });
        this.currentToken = renewRes.data.jwt;
        logger.log(this.currentToken);
    }

    // 检查是否需要验证码
    async captchaRequired() {
        const res = await this.client.post(`${SunoApi.BASE_URL}/api/c/check`, {
            ctype: 'generation'
        });
        return res.data.required;
    }

    // 自动处理验证码（如需要，则用 playwright、ghost-cursor 模拟）
    /**
 * 自动处理验证码：支持 Cloudflare Turnstile (优先)/hCaptcha (兼容)
 */
    async getCaptcha() {
        const needCaptcha = await this.captchaRequired();
        logger.log(`needCaptcha=${needCaptcha}`);
        if (!needCaptcha) return null; // 无需验证码

        logger.log('遇到验证码，正在自动识别类型...');
        const browser = await chromium.launch({ headless: yn(process.env.BROWSER_HEADLESS, { default: true }) });
        const context = await browser.newContext({ userAgent: this.userAgent || undefined });
        const page = await context.newPage();

        await page.goto('https://suno.com/create', { waitUntil: 'domcontentloaded' });
        await page.waitForTimeout(3000); // 页面加载+弹窗触发

        // 1. Cloudflare Turnstile 检测
        let cfIframe = null;
        try {
            cfIframe = await page.waitForSelector('iframe[title*="Cloudflare"]', { timeout: 8000 });
        } catch (e) { /* ignore */ }
        if (!cfIframe) {
            try {
                cfIframe = await page.waitForSelector('iframe[src*="challenges.cloudflare.com"]', { timeout: 8000 });
            } catch (e) { /* ignore */ }
        }
        if (cfIframe) {
            logger.log('检测到 Cloudflare Turnstile 验证码...');

            // 解析sitekey(必须拿到 0x...)
            const cfSrc = await cfIframe.getAttribute('src');
            let sitekey = '';
            try {
                sitekey = cfSrc.match(/\/(0x[a-zA-Z0-9]+)/)[1];
            } catch (e) {
                await browser.close();
                throw new Error('解析Turnstile sitekey失败，src=' + cfSrc);
            }
            // 调用2captcha
            let result;
            try {
                result = await this.solver.turnstile({
                    sitekey,
                    url: 'https://suno.com/create'
                });
            } catch (err) {
                await browser.close();
                throw new Error('2captcha Turnstile 打码失败: ' + err.message);
            }
            logger.log('2captcha返回token：', result.code);

            // 将token填入
            await page.evaluate((token) => {
                const inputs = document.querySelectorAll('input[name="cf-turnstile-response"]');
                for (const input of inputs) input.value = token;
                inputs.forEach(input => {
                    input.dispatchEvent(new Event('input', { bubbles: true }));
                    input.dispatchEvent(new Event('change', { bubbles: true }));
                });
                window['__cf_chl_tk'] = token; // 兼容window变量取token
            }, result.code);

            await page.waitForTimeout(2500); // 提交等待
            await browser.close();
            logger.log('Turnstile自动打码已完成');
            return result.code;
        }

        // 2. hCaptcha 检测，兼容老逻辑
        let hFrame = null;
        try {
            hFrame = await page.waitForSelector('iframe[title*="hCaptcha"]', { timeout: 8000 });
        } catch (e) { }
        if (hFrame) {
            try {
                logger.log('检测到 hCaptcha 验证码...');
                const frame = await hFrame.contentFrame();
                const challengeSel = '.challenge-container';
                await frame.waitForSelector(challengeSel, { timeout: 15000 });
                const challenge = await frame.$(challengeSel);
                if (!challenge) throw new Error('无法定位到验证码挑战区域');

                const imageBuffer = await challenge.screenshot();
                logger.log('图片已截取，正在提交2captcha...');
                let coordinatesResult = await this.solver.coordinates({
                    body: imageBuffer.toString('base64')
                });
                if (!coordinatesResult || !Array.isArray(coordinatesResult.data) || coordinatesResult.data.length === 0) {
                    throw new Error('2captcha未能返回点选坐标');
                }
                logger.log('2captcha识别点:', coordinatesResult.data);

                // 点选所有答案
                for (const pt of coordinatesResult.data) {
                    await challenge.click({ position: { x: Number(pt.x), y: Number(pt.y) } });
                    await page.waitForTimeout(250);
                }
                // 提交
                const submitBtn = await frame.$('.button-submit');
                if (submitBtn) {
                    await submitBtn.click();
                    await page.waitForTimeout(2000);
                }
                await browser.close();
                logger.log('hCaptcha自动打码已完成');
                return '';
            } catch (err) {
                await browser.close();
                throw new Error('hCaptcha自动打码失败：' + err.message);
            }
        }

        // 3. 两种验证码都没有检测到
        await browser.close();
        throw new Error('未检测到需处理的验证码iframe（Cloudflare/hCaptcha均缺失），或页面结构发生变化！');
    }


    // 生成音乐，自动续期 token/cookie，如遇验证码自动处理
    async generate(prompt, makeInstrumental = false, model = 'chirp-v3-5', waitAudio = false) {
        await this.keepAlive();
        
        const payload = {
            make_instrumental: makeInstrumental,
            mv: model,
            prompt: '',
            generation_type: 'TEXT',
            token: await this.getCaptcha()
        };
        payload.gpt_description_prompt = prompt;
        logger.log('Suno生成音乐参数:', payload);
        // 发起生成请求
        // const res = await this.client.post(`${SunoApi.BASE_URL}/api/generate/v2/`, payload, { timeout: 15000 });
        // if (res.status !== 200) throw new Error('Suno生成失败：' + res.statusText);

        // const songIds = res.data.clips.map(item => item.id);
        // // 是否等待音频文件就绪
        // if (waitAudio) {
        //     let startTime = Date.now();
        //     let lastResponse = [];
        //     while (Date.now() - startTime < 100000) {
        //         const tracks = await this.get(songIds);
        //         const allComplete = tracks.every(m => m.status === 'streaming' || m.status === 'complete');
        //         if (allComplete) return tracks;
        //         lastResponse = tracks;
        //         await new Promise(r => setTimeout(r, 3500)); // 等待
        //         await this.keepAlive();
        //     }
        //     return lastResponse;
        // } else {
        //     return res.data.clips.map(audio => ({
        //         id: audio.id,
        //         title: audio.title,
        //         audio_url: audio.audio_url,
        //         status: audio.status,
        //         created_at: audio.created_at,
        //     }));
        // }
    }

    // 查询音乐生成状态（支持批量）
    async get(ids = []) {
        await this.keepAlive();
        let url = `${SunoApi.BASE_URL}/api/feed/v2`;
        if (ids.length) url += '?ids=' + ids.join(',');
        const res = await this.client.get(url, { timeout: 10000 });
        return (res.data.clips || []).map(audio => ({
            id: audio.id,
            title: audio.title,
            image_url: audio.image_url,
            audio_url: audio.audio_url,
            status: audio.status,
            created_at: audio.created_at,
        }));
    }
}

module.exports = SunoApi;
